package com.adv.ems.service.impl;

import cn.hutool.core.collection.ListUtil;
import com.adv.ems.constants.EmsConst;
import com.adv.ems.db.entity.*;
import com.adv.ems.enums.SubjectTypeEnum;
import com.adv.ems.manager.*;
import com.adv.ems.model.dto.UserSubjectInfo;
import com.adv.ems.model.param.EnergySearchOnlineValueParam;
import com.adv.ems.model.param.EnergySearchSubjectTreeParam;
import com.adv.ems.model.param.MeterHealthParam;
import com.adv.ems.model.vo.EnergySearchCircuitDiagramTreeVO;
import com.adv.ems.model.vo.EnergySearchOnlineValueVO;
import com.adv.ems.model.vo.EnergySearchSubjectTreeVO;
import com.adv.ems.model.vo.MeterHealthVO;
import com.adv.ems.service.EnergySearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class EnergySearchServiceImpl extends BaseServiceImpl implements EnergySearchService {
    @Resource
    DepartmentInfoManager departmentInfoManager;
    @Resource
    EnergyInfoManager energyInfoManager;
    @Resource
    CircuitDiagramNodeEnergySubjectRelationManager circuitDiagramNodeEnergySubjectRelationManager;
    @Resource
    GatewayDeviceManager gatewayDeviceManager;
    @Autowired
    private InstituteInfoManager instituteInfoManager;

    @Override
    public EnergySearchCircuitDiagramTreeVO diagramTree(EnergySearchSubjectTreeParam param, String acceptLanguage) {
        EnergySearchCircuitDiagramTreeVO result = new EnergySearchCircuitDiagramTreeVO();

        Map<Long, List<EnergySearchCircuitDiagramTreeVO.Node>> parentIdToChildren = circuitDiagramNodeManager
                .getAllMap()
                .values()
                .stream()
                .sorted(Comparator.comparing(CircuitDiagramNodeDO::getSort))
                .filter(n -> n.getParentId() != null)
                .map(node -> new EnergySearchCircuitDiagramTreeVO.Node(
                        node,
                        circuitDiagramNodeMeterRelationManager.getAllMap().values(),
                        circuitDiagramNodeEnergySubjectRelationManager.getAllMap().values(),
                        meterManager.getAllMap(),
                        buildingInfoManager.getAllMap(),
                        departmentInfoManager.getAllMap(),
                        collectPointManager.getAllMap(),
                        energyInfoManager.getAllMap(),
                        param.getExcludeTypes(),
                        acceptLanguage
                ))
                .collect(Collectors.groupingBy(EnergySearchCircuitDiagramTreeVO.Node::getParentId));

        UserDO currentUser = getCurrentUser();
        if (!StringUtils.isEmpty(currentUser.getRole()) && currentUser.getRole().contains(EmsConst.ADMIN_ROLE_ID)) {
            getTree(result.getList(), new HashSet<>(), parentIdToChildren, param.getExcludeTypes(), acceptLanguage);
        }else {
            UserSubjectInfo userSubjectInfo = getUserSubjectInfo(currentUser);
            if (!CollectionUtils.isEmpty(userSubjectInfo.getInstitute())) {
                Long id = userSubjectInfo.getInstitute().get(0);
                List<EnergySearchCircuitDiagramTreeVO.Node> roots = getRoots(id, null, null, param.getExcludeTypes(), acceptLanguage);
                getTree(roots, new HashSet<>(), parentIdToChildren, param.getExcludeTypes(), acceptLanguage);
                result.getList().addAll(roots);
            }else if (!CollectionUtils.isEmpty(userSubjectInfo.getBuilding())) {
                Long id = userSubjectInfo.getBuilding().get(0);
                List<EnergySearchCircuitDiagramTreeVO.Node> roots = getRoots(null, id, null, param.getExcludeTypes(), acceptLanguage);
                getTree(roots, new HashSet<>(), parentIdToChildren, param.getExcludeTypes(), acceptLanguage);
                result.getList().addAll(roots);

            }else if (!CollectionUtils.isEmpty(userSubjectInfo.getDepartment())) {
                Long id = userSubjectInfo.getDepartment().get(0);
                List<EnergySearchCircuitDiagramTreeVO.Node> roots = getRoots(null, null, id, param.getExcludeTypes(), acceptLanguage);
                getTree(roots, new HashSet<>(), parentIdToChildren, param.getExcludeTypes(), acceptLanguage);
                result.getList().addAll(roots);
            }
        }
        return result;
    }

    @Override
    public EnergySearchOnlineValueVO onlineValue(EnergySearchOnlineValueParam param, String acceptLanguage) {
        EnergySearchOnlineValueVO result = new EnergySearchOnlineValueVO();
        Set<Long> pointIds = new HashSet<>();
        if (param.getPointId() != null) {
            pointIds.add(param.getPointId());
        }else {
            Set<Long> meterIds = new HashSet<>();
            if (param.getMeterId() != null) {
                meterIds.add(param.getMeterId());
            }else {
                Set<Long> rootNodeIds = new HashSet<>();
                if (param.getNodeId() != null) {
                    rootNodeIds.add(param.getNodeId());
                }else if (param.getBuildingId() != null) {
                    List<Long> diagramNodeIds = circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(param.getBuildingId()), SubjectTypeEnum.BUILDING);
                    rootNodeIds.addAll(diagramNodeIds);
                }else if (param.getDepartmentId() != null) {
                    List<Long> diagramNodeIds = circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(param.getDepartmentId()), SubjectTypeEnum.DEPARTMENT);
                    rootNodeIds.addAll(diagramNodeIds);
                }
                if (CollectionUtils.isEmpty(rootNodeIds)) {
                    return result;
                }
                Set<Long> childrenNodeIds = circuitDiagramNodeManager
                        .getAllMap()
                        .values()
                        .stream()
                        .filter(n ->
                            rootNodeIds.stream().anyMatch(id -> n.getPath() != null && n.getPath().contains(EmsConst.PATH_SPLIT + id + EmsConst.PATH_SPLIT))
                        )
                        .map(CircuitDiagramNodeDO::getId)
                        .collect(Collectors.toSet());
                rootNodeIds.addAll(childrenNodeIds);
                List<Long> currentMeterIds = circuitDiagramNodeMeterRelationManager
                        .getAllMap()
                        .values()
                        .stream()
                        .filter(r -> rootNodeIds.contains(r.getDiagramNodeId())).map(CircuitDiagramNodeMeterRelationDO::getMeterId)
                        .collect(Collectors.toList());
                meterIds.addAll(currentMeterIds);
            }
            if (CollectionUtils.isEmpty(meterIds)) {
                return result;
            }
            List<Long> currentPointIds = collectPointManager
                    .getAllMap()
                    .values()
                    .stream()
                    .filter(p -> p.getMeterId() != null && meterIds.contains(p.getMeterId()))
                    .map(CollectPointDO::getId)
                    .collect(Collectors.toList());
            pointIds.addAll(currentPointIds);
        }
        if (CollectionUtils.isEmpty(pointIds)) {
            return result;
        }
        for (Long pointId : pointIds) {
            CollectPointDO pointDO = collectPointManager.getAllMap().get(pointId);
            if (pointDO == null) {
                continue;
            }
            MeterDO meterDO = null;
            ItemInfoDO itemInfoDO = null;
            EnergyInfoDO energyInfoDO = null;
            GatewayDeviceDO gatewayDeviceDO = null;
            if (pointDO.getMeterId() != null) {
                meterDO = meterManager.getAllMap().get(pointDO.getMeterId());
                if (meterDO != null && meterDO.getItemId() != null) {
                    itemInfoDO = itemInfoManager.getAllMap().get(meterDO.getItemId());
                }
            }
            if (meterDO != null && meterDO.getEnergyId() != null) {
                energyInfoDO = energyInfoManager.getAllMap().get(meterDO.getEnergyId());
            }
            if (pointDO.getGatewayId() != null) {
                gatewayDeviceDO = gatewayDeviceManager.getAllMap().get(pointDO.getGatewayId());
            }
            EnergySearchOnlineValueVO.Info info = new EnergySearchOnlineValueVO.Info(meterDO, pointDO, itemInfoDO, energyInfoDO, gatewayDeviceDO, acceptLanguage);
            result.getList().add(info);
        }
        return result;
    }

    @Override
    public EnergySearchSubjectTreeVO subjectTree(EnergySearchSubjectTreeParam param, String acceptLanguage) {
        EnergySearchSubjectTreeVO result = new EnergySearchSubjectTreeVO();
        UserDO currentUser = getCurrentUser();
        if (!StringUtils.isEmpty(currentUser.getRole()) && currentUser.getRole().contains(EmsConst.ADMIN_ROLE_ID)) {
            subjectTree(result.getList(), param.getExcludeTypes(), acceptLanguage);
        }else {
            UserSubjectInfo userSubjectInfo = getUserSubjectInfo(currentUser);
            if (!CollectionUtils.isEmpty(userSubjectInfo.getInstitute())) {
                Long id = userSubjectInfo.getInstitute().get(0);
                EnergySearchSubjectTreeVO.Node root = new EnergySearchSubjectTreeVO.Node(instituteInfoManager.getAllMap().get(id), acceptLanguage);
                result.getList().add(root);
                subjectTree(result.getList(), param.getExcludeTypes(), acceptLanguage);
            }else if (!CollectionUtils.isEmpty(userSubjectInfo.getBuilding())) {
                Long id = userSubjectInfo.getBuilding().get(0);
                EnergySearchSubjectTreeVO.Node root = new EnergySearchSubjectTreeVO.Node(buildingInfoManager.getAllMap().get(id), acceptLanguage);
                result.getList().add(root);
                subjectTree(result.getList(), param.getExcludeTypes(), acceptLanguage);
            }else if (!CollectionUtils.isEmpty(userSubjectInfo.getDepartment())) {
                Long id = userSubjectInfo.getDepartment().get(0);
                EnergySearchSubjectTreeVO.Node root = new EnergySearchSubjectTreeVO.Node(departmentInfoManager.getAllMap().get(id), acceptLanguage);
                result.getList().add(root);
                subjectTree(result.getList(), param.getExcludeTypes(), acceptLanguage);
            }
        }
        return result;
    }

    @Override
    public MeterHealthVO meterHealth(MeterHealthParam param, String acceptLanguage) {
        MeterHealthVO result = new MeterHealthVO();
        if (CollectionUtils.isEmpty(param.getMeterIds())) {
            return result;
        }
        for (Long meterId : param.getMeterIds()) {
            MeterDO meterDO = meterManager.getAllMap().get(meterId);
            if (meterDO == null) {
                continue;
            }
            EnergyInfoDO energy = null;
            if (meterDO.getEnergyId() != null) {
                energy = energyInfoManager.getAllMap().get(meterDO.getEnergyId());
            }
            List<CollectPointDO> points = collectPointManager.getAllMap().values().stream().filter(p -> meterId.equals(p.getMeterId())).collect(Collectors.toList());
            MeterHealthVO.Info info = new MeterHealthVO.Info(meterDO, energy, points, acceptLanguage);
            result.getList().add(info);
        }
        return result;
    }

    private void subjectTree(List<EnergySearchSubjectTreeVO.Node> parent, List<SubjectTypeEnum> excludeTypes, String acceptLanguage) {
        if (parent.isEmpty()) {
            List<EnergySearchSubjectTreeVO.Node> roots = instituteInfoManager
                    .getAllMap()
                    .values()
                    .stream()
                    .filter(i -> i.getParentId() == null)
                    .map(i -> new EnergySearchSubjectTreeVO.Node(i, acceptLanguage))
                    .collect(Collectors.toList());
            if (CollectionUtils.isEmpty(roots)) {
                return;
            }
            parent.addAll(roots);
            subjectTree(parent, excludeTypes, acceptLanguage);
        }else {
            for (EnergySearchSubjectTreeVO.Node node : parent) {
                if (node.getType().equals(SubjectTypeEnum.INSTITUTE)) {
                    List<EnergySearchSubjectTreeVO.Node> children = instituteInfoManager
                            .getAllMap()
                            .values()
                            .stream()
                            .filter(i -> i.getParentId() != null)
                            .filter(i -> i.getParentId().equals(node.getId()))
                            .map(i -> new EnergySearchSubjectTreeVO.Node(i, acceptLanguage))
                            .collect(Collectors.toList());
                    if (!excludeTypes.contains(SubjectTypeEnum.BUILDING)) {
                        List<EnergySearchSubjectTreeVO.Node> buildingChildren = buildingInfoManager
                                .getAllMap()
                                .values()
                                .stream()
                                .filter(b -> node.getId().equals(b.getInstituteId()))
                                .map(b -> new EnergySearchSubjectTreeVO.Node(b, acceptLanguage))
                                .collect(Collectors.toList());
                        children.addAll(buildingChildren);
                    }
                    if (!CollectionUtils.isEmpty(children)) {
                        node.setChildren(children);
                        subjectTree(children, excludeTypes, acceptLanguage);
                    }
                }else if (node.getType().equals(SubjectTypeEnum.BUILDING)) {
                    List<EnergySearchSubjectTreeVO.Node> children = new ArrayList<>();
                    if (!excludeTypes.contains(SubjectTypeEnum.DEPARTMENT)) {
                        List<EnergySearchSubjectTreeVO.Node> deptChildren = departmentInfoManager
                                .getAllMap()
                                .values()
                                .stream()
                                .filter(b -> node.getId().equals(b.getBuildingId()))
                                .map(b -> new EnergySearchSubjectTreeVO.Node(b, acceptLanguage))
                                .collect(Collectors.toList());
                        children.addAll(deptChildren);
                    }
                    if (!excludeTypes.contains(SubjectTypeEnum.GATEWAY)) {
                        List<EnergySearchSubjectTreeVO.Node> gatewayChildren = gatewayDeviceManager
                                .getAllMap()
                                .values()
                                .stream()
                                .filter(b -> node.getId().equals(b.getBuildingId()))
                                .map(b -> new EnergySearchSubjectTreeVO.Node(b, acceptLanguage))
                                .collect(Collectors.toList());
                        children.addAll(gatewayChildren);
                    }
                    if (!excludeTypes.contains(SubjectTypeEnum.METER)) {
                        List<EnergySearchSubjectTreeVO.Node> meterChildren = getAllMeterIdsByBuilding(node.getId())
                                .stream()
                                .map(id -> meterManager.getAllMap().get(id))
                                .filter(Objects::nonNull)
                                .map(b -> new EnergySearchSubjectTreeVO.Node(b, acceptLanguage))
                                .collect(Collectors.toList());
                        children.addAll(meterChildren);
                    }
                    if (!CollectionUtils.isEmpty(children)) {
                        node.setChildren(children);
                        subjectTree(children, excludeTypes, acceptLanguage);
                    }
                }else if (node.getType().equals(SubjectTypeEnum.DEPARTMENT)) {
                    if (!excludeTypes.contains(SubjectTypeEnum.METER)) {
                        List<EnergySearchSubjectTreeVO.Node> children = getMeterIdsByDepartment(node.getId())
                                .stream()
                                .map(id -> meterManager.getAllMap().get(id))
                                .filter(Objects::nonNull)
                                .map(b -> new EnergySearchSubjectTreeVO.Node(b, acceptLanguage))
                                .collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(children)) {
                            node.setChildren(children);
                            subjectTree(children, excludeTypes, acceptLanguage);
                        }
                    }
                }else if (node.getType().equals(SubjectTypeEnum.GATEWAY)) {
                    if (!excludeTypes.contains(SubjectTypeEnum.POINT)) {
                        List<EnergySearchSubjectTreeVO.Node> children = collectPointManager
                                .getAllMap()
                                .values()
                                .stream()
                                .filter(c -> node.getId().equals(c.getGatewayId()))
                                .map(c -> new EnergySearchSubjectTreeVO.Node(c, acceptLanguage))
                                .collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(children)) {
                            node.setChildren(children);
                            subjectTree(children, excludeTypes, acceptLanguage);
                        }
                    }
                }else if (node.getType().equals(SubjectTypeEnum.METER)) {
                    if (!excludeTypes.contains(SubjectTypeEnum.POINT)) {
                        List<EnergySearchSubjectTreeVO.Node> children = collectPointManager
                                .getAllMap()
                                .values()
                                .stream()
                                .filter(c -> node.getId().equals(c.getMeterId()))
                                .map(c -> new EnergySearchSubjectTreeVO.Node(c, acceptLanguage))
                                .collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(children)) {
                            node.setChildren(children);
                            subjectTree(children, excludeTypes, acceptLanguage);
                        }
                    }
                }
            }
        }
    }



    private List<EnergySearchCircuitDiagramTreeVO.Node> getRoots(Long instituteId, Long buildingId, Long departmentId, List<SubjectTypeEnum> excludeTypes, String acceptLanguage) {
        Set<Long> nodeIds = new HashSet<>();
        if (instituteId != null || buildingId != null) {
            List<Long> buildingIds;
            if (instituteId != null) {
                buildingIds = buildingInfoManager
                    .getAllMap()
                    .values()
                    .stream()
                    .filter(b -> instituteId.equals(b.getInstituteId())).map(BuildingInfoDO:: getId)
                    .collect(Collectors.toList());
            }else {
                buildingIds = ListUtil.of(buildingId);
            }
            nodeIds.addAll(circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(buildingIds, SubjectTypeEnum.BUILDING));
            if (CollectionUtils.isEmpty(nodeIds)) {
                return new ArrayList<>();
            }

        }else if (departmentId != null) {
            nodeIds.addAll(circuitDiagramNodeEnergySubjectRelationManager.getDiagramNodeIds(ListUtil.of(departmentId), SubjectTypeEnum.DEPARTMENT));
        }
        List<CircuitDiagramNodeDO> nodes = nodeIds
                .stream()
                .map(nodeId -> circuitDiagramNodeManager.getAllMap().get(nodeId))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        Set<Long> roots = new LinkedHashSet<>();
        for (CircuitDiagramNodeDO node : nodes) {
            if (!StringUtils.hasLength(node.getPath())) {
                roots.add(node.getId());
                continue;
            }
            String[] pathArr = node.getPath().split(EmsConst.PATH_SPLIT);
            Long currentRootId = node.getId();
            for (int i = pathArr.length - 1; i >= 0; i--) {
                Long id = Long.parseLong(pathArr[i]);
                CircuitDiagramNodeDO current = circuitDiagramNodeManager.getAllMap().get(id);
                if (EmsConst.YES.equals(current.getVirtual())) {
                    currentRootId = current.getId();
                }else {
                    break;
                }
            }
            roots.add(currentRootId);
        }
        return roots
                .stream()
                .map(nodeId -> circuitDiagramNodeManager.getAllMap().get(nodeId))
                .filter(Objects::nonNull)
                .map(node -> new EnergySearchCircuitDiagramTreeVO.Node(
                node,
                circuitDiagramNodeMeterRelationManager.getAllMap().values(),
                circuitDiagramNodeEnergySubjectRelationManager.getAllMap().values(),
                meterManager.getAllMap(),
                buildingInfoManager.getAllMap(),
                departmentInfoManager.getAllMap(),
                collectPointManager.getAllMap(),
                energyInfoManager.getAllMap(),
                excludeTypes,
                acceptLanguage
        )).collect(Collectors.toList());
    }

    private void getTree(List<EnergySearchCircuitDiagramTreeVO.Node> parents,
                         Set<Long> existedIds,
                         Map<Long, List<EnergySearchCircuitDiagramTreeVO.Node>> parentIdToChildren,
                         List<SubjectTypeEnum> excludeTypes,
                         String acceptLanguage) {
        if (parents.isEmpty()) {
            List<CircuitDiagramNodeDO> list = circuitDiagramNodeManager
                    .getAllMap()
                    .values()
                    .stream()
                    .sorted(Comparator.comparing(CircuitDiagramNodeDO::getSort))
                    .collect(Collectors.toList());
            Map<Long, List<EnergySearchCircuitDiagramTreeVO.Node>> map = list
                    .stream()
                    .filter(m -> m.getParentId() != null)
                    .map(node -> new EnergySearchCircuitDiagramTreeVO.Node(
                            node,
                            circuitDiagramNodeMeterRelationManager.getAllMap().values(),
                            circuitDiagramNodeEnergySubjectRelationManager.getAllMap().values(),
                            meterManager.getAllMap(),
                            buildingInfoManager.getAllMap(),
                            departmentInfoManager.getAllMap(),
                            collectPointManager.getAllMap(),
                            energyInfoManager.getAllMap(),
                            excludeTypes,
                            acceptLanguage
                    ))
                    .collect(Collectors.groupingBy(EnergySearchCircuitDiagramTreeVO.Node::getParentId));
            List<EnergySearchCircuitDiagramTreeVO.Node> roots = list
                    .stream()
                    .filter(m -> m.getParentId() == null)
                    .map(node -> new EnergySearchCircuitDiagramTreeVO.Node(
                            node,
                            circuitDiagramNodeMeterRelationManager.getAllMap().values(),
                            circuitDiagramNodeEnergySubjectRelationManager.getAllMap().values(),
                            meterManager.getAllMap(),
                            buildingInfoManager.getAllMap(),
                            departmentInfoManager.getAllMap(),
                            collectPointManager.getAllMap(),
                            energyInfoManager.getAllMap(),
                            excludeTypes,
                            acceptLanguage
                    ))
                    .collect(Collectors.toList());
            parents.addAll(roots);
            if (!CollectionUtils.isEmpty(roots)) {
                getTree(parents, new HashSet<>(), map, excludeTypes, acceptLanguage);
            }
        }else {
            for (EnergySearchCircuitDiagramTreeVO.Node parent : parents) {
                existedIds.add(parent.getNodeId());
                List<EnergySearchCircuitDiagramTreeVO.Node> children = parentIdToChildren.getOrDefault(parent.getNodeId(), new ArrayList<>());
                parent.setChildren(children);
                if (!CollectionUtils.isEmpty(children)) {
                    getTree(children, existedIds, parentIdToChildren, excludeTypes, acceptLanguage);
                }
            }

        }
    }
}
